unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls,
  Menus, Clipbrd, Math, LCLType, BaseUnix, TermIO, Types;

// Math is needed for min/max
// LCLType is needed for virtual key definitions
// BaseUnix is needed for file I/O under linux
// TermIO is needed for serial I/O under linux

type

  { TForm1 }

  TForm1 = class(TForm)

    Image1: TImage;
    Image2: TImage;
    Image3: TImage;
    Label1: TLabel;

    MenuItem1A: TMenuItem;
    MenuItem1B: TMenuItem;
    MenuItem2A: TMenuItem;
    MenuItem2B: TMenuItem;
    MenuItem3A: TMenuItem;
      MenuItem3A1: TMenuItem;
      MenuItem3A2: TMenuItem;
    MenuItem3B: TMenuItem;

    MenuDivider1: TMenuItem;
    MenuItem4: TMenuItem;
    MenuItem5: TMenuItem;
      MenuItem5A: TMenuItem;
      MenuItem5B: TMenuItem;
      MenuItem5C: TMenuItem;
      MenuItem5D: TMenuItem;
      MenuItem5E: TMenuItem;
      MenuItem5F: TMenuItem;
      MenuItem5G: TMenuItem;
    MenuItem6: TMenuItem;
      MenuItem6A: TMenuItem;
      MenuItem6B: TMenuItem;
      MenuItem6C: TMenuItem;
    MenuItem7: TMenuItem;
      MenuItem7A: TMenuItem;
      MenuItem7B: TMenuItem;
      MenuItem7C: TMenuItem;
      MenuItem7D: TMenuItem;

    MenuDivider2: TMenuItem;
    MenuItem8: TMenuItem;
    MenuItem9: TMenuItem;
      MenuItem9A: TMenuItem;
      MenuItem9B: TMenuItem;
      MenuItem9C: TMenuItem;
    MenuDivider3: TMenuItem;
    MenuItem10: TMenuItem;

    VTcursor: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Label6: TLabel;
    Label7: TLabel;
    Label8: TLabel;
    Memo1: TMemo;
    Panel1: TPanel;
    Shape1: TShape;

    Timer1: TTimer;
    Timer2: TTimer;
    Timer3: TTimer;
    Timer4: TTimer;

    PopupMenu1: TPopupMenu;
    OpenDialog1: TOpenDialog;
    SaveDialog1: TSaveDialog;
    ApplicationProperties1: TApplicationProperties;

    procedure FormCreate(Sender: TObject);
    procedure ApplicationProperties1Activate(Sender: TObject);
    procedure ApplicationProperties1Deactivate(Sender: TObject);

    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure FormKeyPress(Sender: TObject; var Key: char);
    procedure FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);

    procedure Panel1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    procedure Panel1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure Panel1MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormMouseWheel(Sender: TObject; Shift: TShiftState;
      WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);

    procedure GenericMouseClick(Sender: TObject);

    procedure PopupMenu1Popup(Sender: TObject);
    procedure MenuItem1AClick(Sender: TObject);
    procedure MenuItem1BClick(Sender: TObject);
    procedure MenuItem2AClick(Sender: TObject);
    procedure MenuItem2BClick(Sender: TObject);
    procedure MenuItem3A1and2Click(Sender: TObject);
    procedure MenuItem3BClick(Sender: TObject);
    procedure MenuItem4Click(Sender: TObject);
    procedure MenuItem5MultiClick(Sender: TObject);
    procedure MenuItem6MultiClick(Sender: TObject);
    procedure MenuItem7MultiClick(Sender: TObject);
    procedure MenuItem8Click(Sender: TObject);
    procedure MenuItem9MultiClick(Sender: TObject);

    procedure Timer1Timer(Sender: TObject);
    procedure Timer2Timer(Sender: TObject);
    procedure Timer3Timer(Sender: TObject);
    procedure Timer4Timer(Sender: TObject);
  private

  public

  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

const SoftwareKey='\Software\NZ made\GFXterm';
        BuildName='Graphical VT Terminal (LINUX)';
        BuildDate='(build: 15-april-2020)'+'  *** TEST VERSION ***';

const ROWS=24;         // 24 or 36 or 43
      COLS=80;         // limit:1036 pixels max if 1024 pixel wide physical screen

        pL='      ';   // left packing spaces for ShowMessage calls
        pR='      ';   // right packing spaces for ShowMessage calls

const CommName:string='';
      CommRate:integer=0;

var LogFile:text;

var CSR:TLabel;                       // used as a shortcut to cursor object
    SCR:TCanvas;                      // used as a shortcut to text screen
    GFX:TCanvas;                      // used as a shortcut to graphics screen

const
//    FontIndex:integer=1;               // index of font in FontList, 0=manual
      FGdefault:integer=7;               // default text FG colour
      BGdefault:integer=0;               // default text BG colour
       FGcolour:integer=7;               // text foreground colour
       BGcolour:integer=0;               // text background colour
       TxtStyle:TFontStyles=[];          // text style (underline, bold, etc)
        DimText:boolean=false;           // low intensity flag
        InvText:boolean=false;           // inverse video flag
         DimOpt:integer=0;               // enabled / bright 1 / bright 2
      CursorVis:boolean=true;            // hide/show text cursor
      VTinsMode:boolean=false;           // VT insert / replacement mode

           Xpos:integer=1;               // initial cursor column
           Ypos:integer=1;               // initial cursor row
        Tmargin:integer=1;               // VT100 scroll area top margin
        Bmargin:integer=ROWS;            // VT100 scroll area bottom margin

          lastK:char=#$00;               // last ascii key pressed
          lastC:char=#$00;               // last ascii character printed

         MouseX:integer=-1;              // last mouse X position
         MouseY:integer=-1;              // last mouse Y position

         CRwait:integer=0;               // the number of <CR> characters being waited on

            TS1:int64=0;                 // timestamp of last serial/network port data read
            TS2:int64=0;                 // timestamp of last serial/network port data write
            TS3:int64=0;                 // timestamp of last VT command decoded
            TS4:int64=0;                 // timestamp of start of connection
            TS5:int64=0;                 // timestamp of start of paste operation
            TE5:int64=0;                 // time (elapsed) for last paste operation

//            ErrorText:string='';               // description of error causing a disconnect, etc
//            ErrorType:TMsgDlgType=mtCustom;

                   PB:record
                        str:string;            // paste buffer (characters to paste)
                        len:integer;           // length of buffer
                        idx:integer            // index into buffer
                      end=(str:''; len:0; idx:0);

                   RB:record                   // serial input ring buffer
                        data:array [0..65535] of char;
                        head:integer;
                        tail:integer
                      end=(data:''; head:0; tail:0);

const
//    BreakCounter:integer=-1;           // used to time sending a break
         CONNECTED:integer=0;            // 0 = offline, 1 = USB check,
                                         // 2 = serial,  4 = network
         LOGTOFILE:boolean=false;        // true if logging text to a file
         SKIPPRINT:boolean=false;        // buffer overflow, move cursor but no text o/p
         DEBUGMODE:integer=0;            // select what diagnostics to send to console

         MouseMode:integer=0;            // mouse mode and reporting protocol, 0=disabled

var TextStore:array [1..ROWS] of array [1..COLS] of char;

type str255=string[255];

const CPC:array [0..15] of TColor=(clBlack, clNavy, clGreen, clTeal,
                                   clMaroon, clPurple, clOlive, clSilver,
                                   clGray, clBlue, clLime, clAqua,
                                   clRed, clFuchsia, clYellow, clWhite);
{   (dim)           (bright)
0 = Black 	8 = Gray
1 = Blue 	9 = Light Blue
2 = Green 	A = Light Green
3 = Aqua 	B = Light Aqua     (Cyan)
4 = Red 	C = Light Red
5 = Purple 	D = Light Purple   (Magenta)
6 = Yellow 	E = Light Yellow
7 = White 	F = Bright White
}
const CVT:array [0..15] of TColor=(clBlack, clMaroon, clGreen, clOlive,
                                   clNavy, clPurple, clTeal, clSilver,
                                   clGray, clRed, clLime, clYellow,
                                   clBlue, clFuchsia, clAqua, clWhite);

{       (0-7 = dim, 8-15 = bright)
30	Black
31	Red
32	Green
33	Yellow
34	Blue                  TColor($FF8080) = bright blue, as per teraterm
35	Magenta   (Purple)
36	Cyan      (Aqua)
37	White

note: mmbasic should stop using bright-black (gray) for plain
text in the editor, and instead use dim-white (silver)
}

var  PAL:array [0..15] of TColor;              // holds colour palette, normally copied from CVT



{ TForm1 }


{$I video.inc}
{$I serial.inc}
{$I engine.inc}



procedure ResetTerminal;
begin
  if LOGTOFILE then                   // close log file if open
  begin
    try CloseFile(LogFile) except end;
    LOGTOFILE:=false
  end;

  RB.head:=RB.tail;                   // empty ring buffer

  PB.idx:=0;
  PB.len:=0;
  PB.str:='';                         // empty paste buffer

  DimText:=false;                     // reset text attributes
  InvText:=false;
  TxtStyle:=[];

  FGcolour:=FGdefault;                // reset text colours
  BGcolour:=BGdefault;

  GFX.Brush.Color:=clBlack;           // default brush: clBlack
  GFX.Pen.Color:=clRed;               // default pen: clRed
  GFX.Pen.Width:=1;                   // default line width: 1

  Tmargin:=1;                         // reset VT scroll margins
  Bmargin:=ROWS;

  MouseMode:=0;                       // turn off mouse reporting
  CursorVis:=true;                    // unhide cursor
  VTinsMode:=false;                   // select replacement mode

  clear(1, 1, COLS, ROWS);            // clear text layer
  GFXclear(0, 0, Gw, Gh);             // clear graphics layer
  Form1.Image2.Hide;                  // hide graphics layer
  gotoxy(1,1)                         // home cursor
end;


// returns how much time has elapsed since GetTickCount64 was assigned to counter
function timesince(counter:int64):int64;
begin
  result:=GetTickCount64-counter
end;

(*
// pad a string with n spaces on left and right
function pad(n:integer; S:string):string;
begin
  pad:=StringOfChar(' ', n)+S+StringOfChar(' ', n)
end;
*)

// from milliseconds input, return days, hours, minutes, seconds in a formatted string
function DHMStime(ms:DWORD):string;
var D,H,M,S:integer;
begin
  D:=ms div (24*60*60*1000);
  dec(ms, D*24*60*60*1000);
  H:=ms div (60*60*1000);
  dec(ms, H*60*60*1000);
  M:=ms div (60*1000);
  dec(ms, M*60*1000);
  S:=ms div 1000;
  if D=1  then result:=format('%d day, %.2dh %.2dm %.2ds',[D, H, M, S]) else
  if D<>0 then result:=format('%d days, %.2dh %.2dm %.2ds',[D, H, M, S]) else
  if H<>0 then result:=format('%dh %.2dm %.2ds',[H, M, S]) else
  if M<>0 then result:=format('%dm %.2ds', [M, S]) else
  if S<>1 then result:=IntToStr(S)+' seconds' else
               result:='1 second'
end;






////////////////////////////////////////////////////////////////////////////////
// Timer event handlers (x4)
//
// Timer1: 15mS nominal tick rate
//         flash cursor: 500mS on, 500mS off
//         every 15mS: update status bar and cursor location
//         every 15mS: process incoming serial data into ring buffer
//         handle break signalling to 1455 firmware
// Timer2: 45mS, process ring buffer to screen (max 30mS runtime)
// Timer3: 300ms paste ticker, interval changes to 15ms while pasting
// Timer4: 45ms, one-shot, part of USB connection health test
//
// note: Timer2 may stamp all over the other timers if
//       it needs to process large quantities of data.
////////////////////////////////////////////////////////////////////////////////

procedure TForm1.Timer1Timer(Sender: TObject);
const flag1:boolean=false;
      flag2:boolean=false;
      flag3:integer=-1;
var buffer:str255;                     // (local) serial input buffer
       I64:int64;
       I,J:integer;
         S:string;
begin
// ******** FLASH CURSOR: 500ms on, 500ms off **********************************
// ******** (also check network socket for disconnect) *************************

  flag1:=((GetTickCount64 mod 1000)<500);
  if flag1<>flag2 then                         // change state every 500ms
  begin
    CSR.Visible:=(flag1 or (CSR.Tag=1))        // first 500mS of interval or has been moved
        and CursorVis                          // is not turned off
        and (PB.idx=0);                        // disable cursor while pasting
    CSR.Tag:=0;                                // clear cursor movement flag
    flag2:=flag1;
(*
//  if flag1 and ClientSocket1.Active then try ClientSocket1.Socket.SendText('') except end;
//  ******** the above doesn't work, was an attempt to detect a dropped connection ********

    if (CONNECTED=4) and not ClientSocket1.Active then
    begin
      CONNECTED:=0;
      if timesince(TS4)>5000 then              // only show message if connected for >5 seconds
      begin
        ErrorType:=mtInformation;
        ErrorText:='Network I/O error: connection to server is no longer active'+#13+
                   #13+
                   'connected for '+DHMStime(timesince(TS4))+#13+
                   'last RxD was '+DHMStime(timesince(TS1))+' ago'+#13+
                   'last TxD was '+DHMStime(timesince(TS2))+' ago'
      end
    end
*)
  end;


//  if HideCursor then Cursor.Visible:=false;

// ******** UPDATE WINDOW CAPTION and TASKBAR TEXT *****************************
  if CONNECTED<>flag3 then
  begin
    case CONNECTED of 0:begin                          // disconnected
                          Form1.Caption:=BuildName+'   '+BuildDate;
                          Application.Title:='offline';
//                        Label5.Visible:=false;
                          CommName:='';
                          CommRate:=0
                        end;
                      2:begin                          // connected (serial)
                          Form1.Caption:=CommName+':'+
                                      IntToStr(CommRate)+
                                      '   '+BuildName+
                                      '   '+BuildDate;
                          Application.Title:=CommName+':'+IntToStr(CommRate);
                          Label5.Color:=clLime
                        end;
                      4:begin                          // connected (network)
                          (*
                          Form1.Caption:=ClientSocket1.Host+':'+
                                      IntToStr(ClientSocket1.Port)+
                                      '   '+BuildName+
                                      '   '+BuildDate;
                          Application.Title:=ClientSocket1.Host+':'+
                                             IntToStr(ClientSocket1.Port);
                          Label5.Color:=clYellow
                          *)
                        end
    end;  { of case }
    flag3:=CONNECTED
  end;

// ******** UPDATE STATUS BAR and CURSOR POSITION ******************************
  S:=Format('%.2x  row=%.2d  col=%.2d  key=%.2x',
            [ord(lastC), Ypos, Xpos, ord(lastK)]);
  if Label1.Caption<>S then Label1.Caption:=S;

  I64:=timesince(TS1);
  if I64<60000   then begin
                        S:=Format('%.5d', [I64]);
                        S:=copy(S,1,2)+'.'+S[3]+'s'
                      end else
  if I64<3600000 then begin
                        S:=Format('%.2d%.5d', [I64 div 60000, I64 mod 60000]);
                        S:=copy(S,1,2)+':'+copy(S,3,2)
                      end else
                      S:=' >1h ';
(*
  S:=Format('%.10d', [I64]);
  if I64<1000  then S:=copy(S,8,2)+'0ms' else
  if I64<60999 then S:=copy(S,6,2)+'.'+S[8]+'s' else
                    S:='>1min';
*)
  if Label2.Caption<>S then Label2.Caption:=S;
  Label2.Visible:=(CONNECTED<>0);              // hide Rx timer if not connected

  I64:=timesince(TS2);
  if I64<60000   then begin
                        S:=Format('%.5d', [I64]);
                        S:=copy(S,1,2)+'.'+S[3]+'s'
                      end else
  if I64<3600000 then begin
                        S:=Format('%.2d%.5d', [I64 div 60000, I64 mod 60000]);
                        S:=copy(S,1,2)+':'+copy(S,3,2)
                      end else
                      S:=' >1h ';
(*
  S:=Format('%.10d', [I64]);
  if I64<1000  then S:=copy(S,8,2)+'0ms' else
  if I64<60999 then S:=copy(S,6,2)+'.'+S[8]+'s' else
                    S:='>1min';
*)
  if Label3.Caption<>S then Label3.Caption:=S;
  Label3.Visible:=(CONNECTED<>0);              // hide Tx timer if not connected

  I:=RB.head-RB.tail;
  if I<0 then I:=I+sizeof(RB.data);
  I:=min(99, (I*100) div sizeof(RB.data));

  if I>98 then SKIPPRINT:=true;
  if I<95 then SKIPPRINT:=false;

  S:=Format('%.2d%%', [I]);                    // %age of (64k) ring buffer used
  if Label4.Caption<>S then
  begin
    Label4.Caption:=S;
    if I<10 then Label4.Color:=clSilver else
    if I<40 then Label4.Color:=clAqua else
    if I<70 then Label4.Color:=clYellow
            else Label4.Color:=clRed
  end;

  Label5.Visible:=(CONNECTED<>0);              // hide 'online' indicator if not connected
  Label6.Visible:=LOGTOFILE;                   // hide 'logging' indicator if not logging

  S:=Format('[%.6d]', [min(999999, max(PB.len-(PB.idx-1), 0))]);
  if Label7.Caption<>S then
  begin
    Label7.Caption:=S;
    if timesince(TS3)>500 then Label7.Color:=clLime
                          else Label7.Color:=clAqua
  end;
  Label7.Visible:=(PB.idx<>0);

  S:=Format('(%.2d,%.2d)', [min(max(MouseX, 1), COLS),
                            min(max(MouseY, 1), ROWS)]);
  if Label8.Caption<>S then Label8.Caption:=S;
//Label8.Visible:=(MouseX>=1) and (MouseX<=COLS) and (MouseY>=1) and (MouseY<=ROWS);

  CSR.Top :={ Image1.Top+ }  (CSR.Height*(min(max(Ypos, 1), ROWS)-1));
  CSR.Left:={ Image1.Left+ } (CSR.Width* (min(max(Xpos, 1), COLS)-1));

(*                                                                                     *** update info window ***
  if Form2.Visible then
  begin
    case MouseMode of $0000:S:=' mouse=off';
                      $0001:S:=' X10';
                      $0010:S:=' VT200';
                      $0101:S:=' X10+SGR';
                      $0110:S:=' VT200+SGR';
                      $1001:S:=' X10+URXVT';
                      $1010:S:=' VT200+URXVT'
                       else S:=' ???'
    end;  { of case }
    if VTinsMode then S:=' ins'+S
                 else S:=' ovr'+S;

//  S:=' INS'+' VT200+URXVT';
//  FG=n  BG=n  TM=nn  BM=nn  ins  VT200+URXVT

//  FG=n BG=n TM=nn BM=nn ins VT200+URXVT

//  FG=n  TM=nn  VT200+URXVT
//  BG=n  BM=nn  INS

    S:=Format('FG=%.1d BG=%.1d TM=%.2d BM=%.2d',
              [FGcolour,BGcolour,Tmargin, Bmargin])+S;
    if Form2.Label1.Caption<>S then Form2.Label1.Caption:=S
  end;
*)


// ******** READ FROM COMM PORT, placing characters into ring buffer ***********
  while (CONNECTED=2) and ReadComm(buffer) do
  begin
    for I:=1 to length(buffer) do
    begin
      J:=(RB.head+1) mod sizeof(RB.data);
      if J<>RB.tail then
      begin
///////////////////////////////////////////////////////////////////////////////////////////////////////
        if (buffer[I]=#10) and (CRwait<>0) then dec(CRwait);   // <LF> handshake
///////////////////////////////////////////////////////////////////////////////////////////////////////
        RB.data[RB.head]:=buffer[I];
        RB.head:=J
      end
    end
  end;

// ******** HANDLE BREAK (alt-B) SIGNALLING :SetCommBreak / ClearCommBreak *****             // for Lazarus/LINUX, this handled with tcSendBreak(handle, 0)
(*
  if BreakCounter<>-1 then                         // BreakCounter is active
  begin
    if (BreakCounter=0) and (CONNECTED=2) then
    try
//    SetCommBreak(CommFile)                       // set break                                  *********** break/reset MM
    except end;
    Inc(BreakCounter, Timer1.Interval);

    if BreakCounter>200 then                       // then after 200mS
    begin
      if CONNECTED=2 then
      try
//      ClearCommBreak(CommFile)                   // reset break                                *********** break/reset MM
      except end;
      BreakCounter:=-1
    end
  end;
*)
//////////////////////////////////////////////////////////////
//  the following line is a very crude way of initiating a  //
//  connection that has been specified on the command line  //
//////////////////////////////////////////////////////////////
//  if (CommandSwitch<>0) and (timesince(TS1)>250)then Item1Click(nil);                          // commandline switches not allowed in lunux version

////////////////////////////////////////////////////////////////
//  the following line ensures that Panel1 always has focus   //
//  when being interacted, and hence that mouse wheel events  //
//  are not disrupted. exceptions are:                        //
//  1. when command window is being interacted with,          //
//  2. the select and copy window (Memo1) is displayed,       //
//  3. the application has not got focus (keyboard input)     //
////////////////////////////////////////////////////////////////
//if not Memo1.Visible then Panel1.SetFocus
{
  if ACTIVATED and not (Memo1.Visible or                // not text buffer visible (for copying text)
                      Panel1.Focused)                   // not panel1 is focused
                         then Panel1.SetFocus           // push focus to panel1              // with Lazarus, this can force a 'stay on top' application behaviour

}
end;



procedure TForm1.Timer2Timer(Sender: TObject);     // screen ticker
var I1, I2:integer;                                // 45ms = approx 1/20th sec.
    S1, S2:str255;                                 // fallthrough after 30ms
      mark:int64;
        ch:char;
//       S:string;
begin
(*
  if length(ErrorText)<>0 then     // if there is an error message pending...
  begin
    S:=ErrorText;                          // save error message locally
    ErrorText:='';                         // clear message (to stop recursive calls)
    MessageDlg(S, ErrorType, [mbOk], 0);   // display local copy of error message
    exit
  end;
*)
  mark:=GetTickCount64;

// *** until a maximum elapsed of 30ms, process characters to screen ***
  while (timesince(mark)<30) and (RB.head<>RB.tail) do
  begin
    ch:=RB.data[RB.tail];
    RB.tail:=(RB.tail+1) mod sizeof(RB.data);
                                                   // ***********************************************
    S1:=VT100engine(ch);                           // *** first process throught the VT100 engine ***
                                                   // ***********************************************
    for I1:=1 to length(S1) do
    begin
                                                   // *******************************************
      S2:=GFXengine(S1[I1]);                       // *** then process through the GFX engine ***
                                                   // *******************************************

      for I2:=1 to length(S2) do
      if S2[I2]=#05 then begin                     // ENQ -> ACK
                           if PB.idx=0 then
                           case CONNECTED of 2:WriteComm(#06);                                   // GFX handshaking, need to re-enable when comms working
                                             4:;   // WriteSocket(#06)
                           end  { of case }
                         end
                    else emit(S2[I2])              // lastly print out the remainder
    end
  end
end;



procedure TForm1.Timer3Timer(Sender: TObject);     // paste ticker (variable)
const skip:byte=0;                                 // slow past + CR -> skip a few interrupts
        ch:char=#00;                               // last character pasted
var S:str255;
    I:integer;
begin
  if (PB.idx=0) and (Timer3.Interval<>300) then Timer3.Interval:=300;
                                                   // idles at 300ms

  if (PB.idx<>0) and (CONNECTED=0) then begin      // kill paste operation if disconnected
                                          PB.idx:=0;
                                          PB.len:=0;
                                          PB.str:='';
                                          TE5:=timesince(TS5)
                                        end;

  if (PB.idx<>0) and (CONNECTED in [2,4]) then
  begin
    if PB.idx=1 then begin
                       Timer3.Interval:=15;        // ramp up to 15ms intervals
                       TS3:=GetTickCount64;        // force slow paste initially
                       TS5:=TS3;                   // timestamp start of paste operation
                       ch:=#00                     // no 'last' character loaded
                     end;

    if skip<>0 then begin
                      dec(skip);
                      exit
                    end;

    if timesince(TS3)>1000 then                    // *** fast paste ***
    begin                                          // ==================
      if timesince(TS1)>180 then CRwait:=0;        // 180ms timeout on seeing a handshake
                                                   // ( send <CR>  ->  receive <LF> )
//    if CRwait>0 then Form2.RichEdit1.Lines.Add('waiting at '+IntToStr(PB.idx));

      if CRwait<1 then
      begin
        S:='';
        I:=PB.idx;
        repeat
          ch:=PB.str[I];

          if ch=#13 then inc(CRwait);
          S:=S+ch;

          inc(I)
        until (I>PB.len) or (ch<#32) or            // grab a line up to and incl CR or LF
              (length(S)>=160) or                  // split lines > 160 characters long
        ((CommRate>120000) and (length(S)>=40));   // 40 char split at higher baud rates

        case CONNECTED of 2:if WriteComm(S) then PB.idx:=I;
                          4:;  // if WriteSocket(S) then PB.idx:=I                                  // re-enable when network comms working
        end  { of case }
      end
    end

    else                                           // *** slow paste ***
    begin                                          // ==================
      if not((ch=#13) and (timesince(TS1)<300)) then
      begin                                        // 300ms silence req'd after we've sent a <CR>
        ch:=PB.str[PB.idx];
        if ch=#13 then skip:=2;                    // introduce a 45ms delay after sending a <CR>
                                                   // to allow the editor to start responding.
//      if ch=#13 then inc(CRwait);                // note: editor will not respond to <CR>
                                                   // with anything that makes sense to us
        case CONNECTED of 2:if WriteComm(ch) then inc(PB.idx);
                          4:;  // if WriteSocket(ch) then inc(PB.idx)                                // re-enable when network comms working
        end  { of case }
      end
    end;

    if (PB.idx>PB.len) then                        // have finished pasting
    begin
      PB.idx:=0;
      PB.len:=0;
      PB.str:='';
      TE5:=timesince(TS5)
    end
  end
// writeln('active control is: ',TButton(Screen.ActiveControl).Name)

end;



procedure TForm1.Timer4Timer(Sender: TObject);
begin
  if (CONNECTED=2) and not FileExists(CommName) then
  begin
    CONNECTED:=0;
    try fpClose(SerialHandle) except end;
    ShowMessage(#13+pL+'Serial device symlink missing from /dev'+pR+#13+
                #13+
                    pL+'If you are using a USB to serial bridge,'+pR+#13+
                    pL+'please check that the USB cable has not'+pR+#13+
                    pL+'been unplugged.'+pR+#13);
  end
end;







////////////////////////////////////////////////////////////////////////////////
// Keyboard and Mouse Events
// =========================
////////////////////////////////////////////////////////////////////////////////

// *** handle normal ascii keys ***
procedure TForm1.FormKeyPress(Sender: TObject; var Key: char);
begin
  if Memo1.Visible then exit;                  // ignore keys if text buffer visible
//windows.beep(880,50);

  lastK:=Key;
  if PB.idx=0 then case CONNECTED of 0:emit(Key);
                                     2:WriteComm(Key);
                                     4:;   // WriteSocket(Key)                                      // patch into network comms
                   end  { of case }
end;



// *** handle special keys that need to be auto-repeating ***
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var S:string;
    I:integer;
begin
  if Memo1.Visible then exit;                  // ignore keys if text buffer visible
//windows.beep(880,50);

  S:='';
  case Key of VK_LEFT:S:=#27+'[D';
                VK_UP:S:=#27+'[A';
             VK_RIGHT:S:=#27+'[C';
              VK_DOWN:S:=#27+'[B';
             VK_PRIOR:S:=#27+'[5~';
              VK_NEXT:S:=#27+'[6~';
              VK_HOME:S:=#27+'[1~';
               VK_END:S:=#27+'[4~';
            VK_INSERT:S:=#27+'[2~';
            VK_DELETE:S:=#127;                 // convert delete key to chr(127)
               VK_F10:Key:=0;                  // supress f10 popping up right-click menu
            VK_ESCAPE:;  //if NetworkSearching then NetworkErrorFlag:=true
                                               // kludge to escape from loop while connecting                   // socket specific
  end;  { of case }

  if (ssAlt in Shift) and (Key=VK_RETURN) then S:=#13#10;      // alt-enter -> cr+lf
  if (ssAlt in Shift) and (Key=ord('0')) then begin
                                                S:=#0;         // alt-0 -> nul
                                                lastK:=#0
                                              end;

  if (PB.idx=0) and (length(S)>0) then
  case CONNECTED of 0:begin
                        if S[1]=#27 then S:=' esc'+'"'+S+'" ';
                        for I:=1 to length(S) do emit(S[I])
                      end;
                    2:WriteComm(S);
                    4:;  // WriteSocket(S)                                                             // patch into network comms
  end  { of case }
end;


// *** handle non-repeating keys upon release ***
procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
var S:string;
    I:integer;
const RTS_flag:integer=TIOCM_RTS;
      DTR_flag:integer=TIOCM_DTR;
begin
  if Memo1.Visible then                        // ignore keys if text buffer visible
  begin
    if (Key=13) or (Key=27) then               // except hide Memo1 on ESC or CR
    begin
      Form1.PopupMenu:=PopupMenu1;             // re-enable our popup menu

      Memo1.Lines.Clear;
      Memo1.Enabled:=false;                    // probably not needed
      Memo1.Visible:=false
//    Form1.SetFocus
    end;
    exit
  end;
//windows.beep(880,50);

// ssShift	The Shift key is held down.
// ssAlt	The Alt key is held down.
// ssCtrl	The Ctrl key is held down.

  S:='';                                       // default action: do nothing

// *** shifted and unshifted function keys ***
  if not (ssAlt in Shift) and not (ssCtrl in Shift) then       // disallow alt and ctrl
  if ssShift in Shift
  then case Key of VK_F3:S:=#27+'[25~';        // shifted function keys
                   VK_F4:S:=#27+'[26~';
                   VK_F5:S:=#27+'[28~';
                   VK_F6:S:=#27+'[29~';
                   VK_F7:S:=#27+'[31~';
                   VK_F8:S:=#27+'[32~';
                   VK_F9:S:=#27+'[33~';
                  VK_F10:S:=#27+'[34~'
       end  { of case }
  else case Key of VK_F1:S:=#27+'[11~';        // unshifted function keys
                   VK_F2:S:=#27+'[12~';
                   VK_F3:S:=#27+'[13~';
                   VK_F4:S:=#27+'[14~';
                   VK_F5:S:=#27+'[15~';
                   VK_F6:S:=#27+'[17~';
                   VK_F7:S:=#27+'[18~';
                   VK_F8:S:=#27+'[19~';
                   VK_F9:S:=#27+'[20~';
                  VK_F10:S:=#27+'[21~';
                  VK_F11:S:=#27+'[23~';
                  VK_F12:S:=#27+'[24~'
       end;  { of case }

// *** alt keys (letters and numbers), these are all 'special' commands ***
  if (ssAlt in Shift) and not (ssCtrl in Shift) and not (ssShift in Shift) then
  begin
    case Key of ord('B')://BreakCounter:=0;                      // try to reset micromite
                         tcSendBreak(SerialHandle, 0);
                ord(' '):ShowMessage(#13+pL+'sorry, not here yet'+pR+#13);
                         // Item1Click(nil);                      // attempt to reconnect                // ################### MISSING ############
                ord('P'):ShowMessage(#13+pL+'sorry, not here yet'+pR+#13);
                         // Item3Click(Item3A1);                  // paste from clipboard                // ################### MISSING ############
                ord('C'):begin
                           GFXclear(0, 0, Gw, Gh);             // clear graphics layer
                           Image2.Hide                         // hide graphics layer
                         end;
                ord('D'):clear(1, 1, COLS, ROWS);              // clear text layer
                ord('Z'):begin                                 // cancel paste
                           PB.idx:=0;
                           PB.len:=0;
                           PB.str:=''
                         end;
                ord('A'):RB.head:=RB.tail;                     // clear ring buffer
                ord('L'):if LOGTOFILE then try                 // stop logging
                                             LOGTOFILE:=false;
                                             CloseFile(LogFile)
                                           except {ShowMessage(#13+pL+'Failed to close log file'+pR+#13)} end
                                      else try                 // resume logging
                                             Append(LogFile);
                                             LOGTOFILE:=true
                                           except ShowMessage(#13+pL+'Failed to append to log file'+pR+#13) end;
                ord('R'):ResetTerminal;                        // reset whole terminal system
                ord('U'):begin
                           CursorVis:=not CursorVis;             // toggle cursor visible/hidden
                           CSR.Visible:=CursorVis                // turn actual cursor on/off immediately
                         end;

                ord('M'):PopupMenu1.Popup(Mouse.CursorPos.x, Mouse.CursorPos.Y);

                ord('1'):FpIOCtl(SerialHandle, TIOCMBIS, @DTR_flag);
                         // try EscapeCommFunction(CommFile, SETDTR) except end;  // set DTR
                ord('2'):FpIOCtl(SerialHandle, TIOCMBIC, @DTR_flag);
                         // try EscapeCommFunction(CommFile, CLRDTR) except end;  // clear DTR
                ord('3'):FpIOCtl(SerialHandle, TIOCMBIS, @RTS_flag);
                         // try EscapeCommFunction(CommFile, SETRTS) except end;  // set RTS
                ord('4'):FpIOCtl(SerialHandle, TIOCMBIC, @RTS_flag);
                         // try EscapeCommFunction(CommFile, CLRRTS) except end   // clesr RTS

    end  { of case }                               // don't try using alt-0 in here, as it is
  end;                                             // set to generate nul in TForm1.FormKeyDown

// ######################################################################
// the following key actions are for testing purposes only. there is NO
// guarantee that any given function will remain the same, or be present,
// between different releases of GFXterm.
// ######################################################################

// *** ctrl-n (numbers only) ***
  if (ssCtrl in Shift) and not (ssAlt in Shift) and not (ssShift in Shift) then
  begin
    case Key of ord('1'):begin                                 // ctrl-1
                           Panel1.DoubleBuffered:=false;       // turn off double buffering
                           ShowMessage(#13+pL+'Double Buffering is OFF'+pR+#13)
                         end;
                ord('2'):begin                                 // ctrl-2
                           Panel1.DoubleBuffered:=true;        // turn on double buffering
                           ShowMessage(#13+pL+'Double Buffering is ON'+pR+#13)
                         end;
                ord('3'):begin                                 // ctrl-3
                           PAL[0]:=$00FFFFFF-PAL[0];           // swap black and white
                           PAL[15]:=$00FFFFFF-PAL[15];
                           Shape1.Pen.Color:=PAL[0]
                         end;
(*
                         begin
                           for I:=0 to 15 do PAL[I]:=$00FFFFFF-PAL[I];
                           Shape1.Pen.Color:=PAL[0]
                         end;
*)
                ord('4'):begin                                 // ctrl-4
                           if CONNECTED=4 then CONNECTED:=0;
//                           try ClientSocket1.Close except end; // force network disconnect                               // need to put back in when networking sorted
//                           ShowMessage(#13+pL+'Client Socket has been CLOSED'+pR+#13)
                         end;
                ord('6'):begin                                 // ctrl-6
                           S:=#13+pL+CSR.Font.Name+pR+#13+#13;
//                         S:=S+pad(12,'FontIndex = '+IntToStr(FontIndex))+#13;
//                         if CSR.AutoSize then S:=S+pad(12,'AutoSize = ON')+#13
//                                         else S:=S+pad(12,'(AutoSize = OFF')+#13;
                           S:=S+pL+'('+IntToStr(CSR.Width)+' x '+
                                           IntToStr(CSR.Height)+')'+pR+#13;
                           if CONNECTED<>0 then
                               S:=S+#13+pL+'connected for '+DHMStime(timesince(TS4))+pR+#13+
                                        pL+'last RxD was '+DHMStime(timesince(TS1))+' ago'+pR+#13+
                                        pL+'last TxD was '+DHMStime(timesince(TS2))+' ago'+pR+#13+
                                    #13+
                                        pL+'last paste took '+IntToStr(TE5 div 1000)+'.'
                                                            +IntToStr((TE5 mod 1000) div 100)
                                                           +' seconds'+pR+#13;
                           ShowMessage(S);
                           S:=''
                         end;
                ord('9'):begin                                 // ctrl-9                                        // ################### MISSING ############
(*
                           Reg:=TRegistry.Create;
                           with Reg do                         // remove registry key
                           begin
                             RootKey:=HKEY_CURRENT_USER;
                             try DeleteKey(SoftwareKey) except end;
                           end;
                           Reg.Free;
*)
                           ShowMessage(#13+pL+'Registry Data has been CLEARED'+pR+#13)
                         end
    end  { of case }
  end;

  if (PB.idx=0) and (length(S)>0) then
  case CONNECTED of 0:begin
                        if S[1]=#27 then S:=' esc'+'"'+S+'" ';
                        for I:=1 to length(S) do emit(S[I])
                      end;
                    2:WriteComm(S);
                    4:;  // WriteSocket(S)                                                           // need to be patched back into network comms
  end  { of case }
end;







// *** MOUSE MOVE ***
procedure TForm1.Panel1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  if Sender=CSR then begin
                       MouseX:=min(Xpos, COLS);      // fix: can be =81 if at end of line
                       MouseY:=Ypos
                     end
                else begin
                       MouseX:=trunc(((X { -Image1.Left } )/CSR.Width)+1);
                       MouseY:=trunc(((Y { -Image1.Top  } )/CSR.Height)+1)
                     end
end;

// *** MOUSE DOWN ***
procedure TForm1.Panel1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var flags:byte;
        S:string;
begin
  if (CONNECTED in [2,4]) and (MouseMode>0) and (Button=mbLeft) and (PB.idx=0) then
  begin
    if sender=CSR    then begin
                            MouseX:=min(Xpos, COLS);           // fix: can be =81 if at end of line
                            MouseY:=Ypos
                          end else
    if sender=Panel1 then begin
                            MouseX:=trunc(((X { -Image1.Left } )/CSR.Width)+1);
                            MouseY:=trunc(((Y { -Image1.Top  } )/CSR.Height)+1)
                          end
                     else writeln('unexpected mouse down event from '+TButton(Sender).Name);

//  ShowMessage(IntToStr(MouseX)+','+IntToStr(MouseY))
    if (MouseX>=1) and (MouseX<=COLS) and (MouseY>=1) and (MouseY<=ROWS) then
    begin
      flags:=0;
      if ssShift in Shift then inc(flags, 4);
      if ssAlt in Shift then inc(flags, 8);
      if ssCtrl in Shift then inc(flags, 16);

      case MouseMode of $0001:S:=#27+'[M '+chr(32+MouseX)+chr(32+MouseY);                  // X10
                        $0101:S:=#27+'[<0;'+IntToStr(MouseX)+';'+IntToStr(MouseY)+'M';     // X10/SGR
                        $1001:S:=#27+'[32;'+IntToStr(MouseX)+';'+IntToStr(MouseY)+'M';     // X10/URXVT

                        $0010:S:=#27+'[M'+chr(32+flags)+chr(32+MouseX)+chr(32+MouseY);                     // VT200
                        $0110:S:=#27+'[<'+IntToStr(flags)+';'+IntToStr(MouseX)+';'+IntToStr(MouseY)+'M';   // VT200/SGR
                        $1010:S:=#27+'['+IntToStr(32+flags)+';'+IntToStr(MouseX)+';'+IntToStr(MouseY)+'M'  // VT200/URXVT
                     else S:=''
      end ; { of case }

      if (PB.idx=0) and (length(S)>0) then
      case CONNECTED of 2:WriteComm(S);
                        4:; // WriteSocket(S)                                                                 // patch back in when networking working
      end  { of case }
    end
  end
end;


/// *** MOUSE UP ***
procedure TForm1.Panel1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var flags:byte;
        S:string;
begin
  if (CONNECTED in [2,4]) and (MouseMode>0) and (Button=mbLeft) and (PB.idx=0) then
  begin
    if sender=CSR    then begin
                            MouseX:=min(Xpos, COLS);           // fix: can be =81 if at end of line
                            MouseY:=Ypos
                          end else
    if sender=Panel1 then begin
                            MouseX:=trunc(((X { -Image1.Left } )/CSR.Width)+1);
                            MouseY:=trunc(((Y { -Image1.Top  } )/CSR.Height)+1)
                          end
                     else writeln('unexpected mouse up event from '+TButton(Sender).Name);

//  ShowMessage(IntToStr(MouseX)+','+IntToStr(MouseY))
    if (MouseX>=1) and (MouseX<=COLS) and (MouseY>=1) and (MouseY<=ROWS) then
    begin
      flags:=0;
      if ssShift in Shift then inc(flags, 4);
      if ssAlt in Shift then inc(flags, 8);
      if ssCtrl in Shift then inc(flags, 16);

      case MouseMode of $0010:S:=#27+'[M'+chr(32+3+flags)+chr(32+MouseX)+chr(32+MouseY);                       // VT200
                        $0110:S:=#27+'[<'+IntToStr(flags)+';'+IntToStr(MouseX)+';'+IntToStr(MouseY)+'m';       // VT200/SGR
                        $1010:S:=#27+'['+IntToStr(32+3+flags)+';'+IntToStr(MouseX)+';'+IntToStr(MouseY)+'M'    // VT200/URXVT
                     else S:=''
      end ; { of case }

      if (PB.idx=0) and (length(S)>0) then
      case CONNECTED of 2:WriteComm(S);
                        4:;  // WriteSocket(S)                                                                 // patch back in when networking working
      end  { of case }
    end
  end
end;


// *** MOUSE WHEEL ***
procedure TForm1.FormMouseWheel(Sender: TObject; Shift: TShiftState;
  WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
var S:string;
begin
  if Memo1.Visible then exit;                  // ignore mouse wheel if text buffer visible

  if WheelDelta>0 then S:=#27+'[A' else
  if WheelDelta<0 then S:=#27+'[B' else S:='';

  if (PB.idx=0) and (length(S)>0) then
  case CONNECTED of 2:WriteComm(S);
                    4:;  // WriteSocket(S)                                                                 // patch back in when networking working
  end; { of case }
//windows.beep(880,50);
  Handled:=true
end;





////////////////////////////////
// right-click menu controls ///
////////////////////////////////
procedure TForm1.PopupMenu1Popup(Sender: TObject);
begin
  MenuItem1A.Visible:=(CONNECTED=0);
  MenuItem1B.Visible:=(CONNECTED<>0);
  MenuItem2A.Visible:=not LOGTOFILE;
  MenuItem2B.Visible:=LOGTOFILE;
  MenuItem3A.Visible:=(PB.idx=0);
  MenuItem3B.Visible:=(PB.idx<>0)
end;

// connect
procedure TForm1.MenuItem1AClick(Sender: TObject);
const S:string='';
begin
  if InputQuery('CONNECT', 'enter port name (/dev/ttyXXXn) ', S) then SetupCommPort(S, 38400, 8, 0, 1)
//  SetupCommPort('/dev/ttyACM0', 38400, 8, 0, 1)
end;

// disconnect
procedure TForm1.MenuItem1BClick(Sender: TObject);
begin
  CloseCommPort
end;

// log to file
procedure TForm1.MenuItem2AClick(Sender: TObject);
begin

  if SaveDialog1.Execute then
  try
    AssignFile(LogFile, SaveDialog1.Filename);
    ReWrite(LogFile);
    LOGTOFILE:=true
  except
    LOGTOFILE:=false;
    try CloseFile(LogFile) except end;
//    ErrorType:=mtWarning;
//    ErrorText:='Could not open file:'+#13+
//               ExtractFileName(SaveDialog1.FileName)+#13
    SHowMessage(#13+pL+'Could not open file:'+pR+#13+
                    pL+ExtractFileName(SaveDialog1.FileName)+pR+#13)
  end
end;

// stop logging
procedure TForm1.MenuItem2BClick(Sender: TObject);
begin
  LOGTOFILE:=false;
  try CloseFile(LogFile) except end
end;

// paste from clipboard or file
procedure TForm1.MenuItem3A1and2Click(Sender: TObject);
var SL:TStringList;
     S:string;
     I:integer;
begin
// setup for paste from clipboard
  if Sender=MenuItem3A1 then begin
                               if PB.idx<>0 then exit;
                               if ClipBoard.HasFormat(CF_TEXT) then
                               try
                                 S:=ClipBoard.AsText;
                               except
                                 S:=''
//                               ShowMessage('ClipBoard exception')
                               end
                             end;

// setup for paste from text file
  if Sender=MenuItem3A2 then begin
                               if PB.idx<>0 then exit;
                               if OpenDialog1.Execute then
                               begin
                                 SL:=TStringlist.Create;
                                 try
                                   SL.LoadFromFile(OpenDialog1.Filename);
                                   S:=SL.Text
                                 except
                                   S:=''
//                                 ShowMessage('LoadFromFile exception')
                                 end;
                                 SL.Free
                               end
                             end;

// common code for both types of paste (from clipboard and from text file)

  for I:=1 to length(S) do             // clear bit-7 of all characters being pasted
        S[I]:=char(byte(S[I]) and $7F);

  I:=pos(#13#10, S);
  while I<>0 do
  begin                                // translate all CR-LF pairs into single CR
    delete(S, I+1, 1);
    I:=pos(#13#10, S)
  end;

  for I:=1 to length(S) do             // translate any remaining LFs into CRs
      if S[I]=#10 then S[I]:=#13;

  for I:=length(S) downto 1 do         // remove any other control characters (except crtl-Z)
      if S[I] in [#0..#12,#14..#25,#27..#31  {,#127..#255}  ] then delete(S, I, 1);

  I:=pos(#32#13, S);
  while I<>0 do
  begin                                // remove all trailing spaces at ends of lines
    delete(S, I, 1);
    I:=pos(#32#13, S)
  end;

  while (length(S)<>0) and (S[length(S)]=#32) do delete(S, length(S), 1);
                                       // remove final trailing spaces at end of S
(*
  for I:=1 to length(S) do             // translate all CRs into LFs as
      if S[I]=#13 then S[I]:=#10;      // test for unix formatted files
*)
(*
  for I:=1 to length(S) do             // set bit-7 of all characters being pasted
      S[I]:=char(byte(S[I]) or $80);   // test for ignoring bit-7 set
*)

  if length(S)<>0 then
  begin
    PB.str:=S;                         // load data into string ready to be streamed
    PB.len:=length(S);                 // out of the comm port by a timer interrupt
    PB.idx:=1
  end
end;


// cancel pasting
procedure TForm1.MenuItem3BClick(Sender: TObject);
begin
  PB.idx:=0;
  PB.len:=0;
  PB.str:=''
end;

// select screen font and size
procedure TForm1.MenuItem4Click(Sender: TObject);
begin
  ShowMessage(#13+pL+'sorry, not here yet'+pR+#13)
                                                                                   // ################### MISSING ############
end;

// popup menu item - select default text colour
procedure TForm1.MenuItem5MultiClick(Sender: TObject);
var TC:integer;
begin
  if Sender=MenuItem5A then begin TC:=1; MenuItem5A.Checked:=true end else
  if Sender=MenuItem5B then begin TC:=2; MenuItem5B.Checked:=true end else
  if Sender=MenuItem5C then begin TC:=3; MenuItem5C.Checked:=true end else
  if Sender=MenuItem5D then begin TC:=4; MenuItem5D.Checked:=true end else
  if Sender=MenuItem5E then begin TC:=5; MenuItem5E.Checked:=true end else
  if Sender=MenuItem5F then begin TC:=6; MenuItem5F.Checked:=true end else
  if Sender=MenuItem5G then begin TC:=7; MenuItem5G.Checked:=true end
                       else       TC:=7;
  FGdefault:=TC;
  FGcolour:=TC
end;

// popup menu item - select how dim attribute is handled (8 vs 16 colour mode)
procedure TForm1.MenuItem6MultiClick(Sender: TObject);
var TD:integer;
begin
  if Sender=MenuItem6A then begin TD:=0; MenuItem6A.Checked:=true end else     // dim attribute enabled
  if Sender=MenuItem6B then begin TD:=1; MenuItem6B.Checked:=true end else     // force bright 1
  if Sender=MenuItem6C then begin TD:=2; MenuItem6C.Checked:=true end          // force bright 2
                       else       TD:=0;
  Dimopt:=TD
end;

// popup menu item - clear graphics layer / text layer / ring buffer
procedure TForm1.MenuItem7MultiClick(Sender: TObject);
begin
  if Sender=MenuItem7A then begin
                          GFXclear(0, 0, Gw, Gh);              // clear graphics layer
                          Image2.Hide                          // hide graphics layer
                        end else
  if Sender=MenuItem7B then clear(1, 1, COLS, ROWS) else       // clear text layer
  if Sender=MenuItem7C then RB.head:=RB.tail else              // clear ring buffer
  if Sender=MenuItem7D then ResetTerminal
end;

// show 'select and copy' edit box
procedure TForm1.MenuItem8Click(Sender: TObject);
var X, Y:integer;
       S:string;
//    TR:TRect;
begin
  Memo1.Width:=CSR.Width*COLS;
  Memo1.Height:=CSR.Height*ROWS;

  Memo1.Font:=CSR.Font;
  Memo1.Font.Style:=[];
  Memo1.Font.Color:=clBlack;                   // black text on a
  Memo1.Color:=clSilver;                       // silver background

  Memo1.Lines.Clear;                           // ensure memo is completely blank

  for Y:=1 to ROWS do                          // fill in memo text
  begin
    S:=StringOfChar(' ', COLS);                // blank line of 80 chars
    for X:=1 to COLS do
    begin
      S[X]:=TextStore[Y, X];                   // copy text from buffer (TextStore)
      if S[X]<' ' then S[X]:=' '               // turn control chars into whitespace
    end;
    S:=TrimRight(S);
//  writeln(S);
    Memo1.Lines.Add(S)                         // transfer completed line into memo
  end;

  S:=Memo1.Text;

  while S[length(S)]<#32 do
      S:=copy(S, 1, length(S)-1);              // remove final control characters from memo
//S:=copy(S, 1, length(S)-2);

  Memo1.Text:=S;

//  TR:=Memo1.ClientRect;                                        // get rid of
//  SendMessage(Memo1.Handle, EM_SETRECT, 0, integer(@TR));      // those pesky
//  Memo1.Invalidate;                                            // margins!!!
//  above lines are win32-specific, and possibly are not needed anyway for a boarderless TMemo

  Form1.PopupMenu:=nil;                        // disable our popup menu
                                               // (cut/copy menu takes over)
  Memo1.Enabled:=true;
  Memo1.Visible:=true
end;

// diagnostics information
procedure TForm1.MenuItem9MultiClick(Sender: TObject);
begin
  if Sender=MenuItem9A then begin DEBUGMODE:=1; MenuItem9A.Checked:=true end else
  if Sender=MenuItem9B then begin DEBUGMODE:=2; MenuItem9B.Checked:=true end
                       else begin DEBUGMODE:=0; MenuItem9C.Checked:=true end
end;





procedure TForm1.FormCreate(Sender: TObject);
var I // ,n1,n2,n3
                :integer;
//            SL:TstringList;
//            RS:TResourceStream;
//            DW:DWORD;
             S:string;
             T:textfile;
begin
  CSR:=Form1.VTcursor;

//Application.HintHidePause:=5000;
  Form1.Caption:=BuildName;            // updated by Timer1
  Application.Title:='GFXterm';        // updated by Timer1

  for I:=0 to 15 do PAL[I]:=CVT[I];    // load default palette values

  S:=ChangeFileExt(ExpandFileName(paramstr(0)),'.PAL');
  if FileExists(S) then
  try
    assignfile(T, S);
    Reset(T);
//  ReWrite(T);                        // use to create sample pallette file

    for I:=0 to 15 do
    begin
//    Writeln(T,'0x'+Format('%.6x',[PAL[I]]));       // write to sample file
      Readln(T,S);
      if S='' then Readln(T,S);
      if pos(' ', S)<>0 then S:=copy(S, 1, pos(' ', S)-1);
      PAL[I]:=StrToInt(S)
    end;
    CloseFile(T)
  except
    for I:=0 to 15 do PAL[I]:=CVT[I];
    try CloseFile(T) except end;
//    ErrorType:=mtWarning;
//    ErrorText:='    Error reading .PAL file,   '+#13+
//               '    using default VT palette    '
    ShowMessage(#13+pL+'Error reading .PAL file, using default VT palette'+pR+#13)
  end;

  Form1.AutoSize:=true;
  Panel1.AutoSize:=true;

//Form1.AutoScroll:=false;             // set in form view
//Form1.KeyPreview:=true;              // set in form view
//Form1.DoubleBuffered:=true;          // leave unset in form view, below line replaces this
  Panel1.DoubleBuffered:=true;         // only double-buffer Panel1 instead of whole form

////////////////////////////////////////////////////////////////////////////////
// start of code to rearrange and resize components for display
////////////////////////////////////////////////////////////////////////////////
  Shape1.Left:=0;
  Shape1.Top:=Label1.Height+2;
  Shape1.Width:=CSR.Width*COLS           +4;   // +4 -> 2 pixel boarder all around
  Shape1.Height:=CSR.Height*ROWS         +4;
  Shape1.Pen.Color:=PAL[0];

  Panel1.Left:=0                         +2;   // +2 -> centre in boarder (Shape1)
  Panel1.Top:=Label1.Height+2            +2;

  for I:=0 to ComponentCount-1 do if Components[I] is TLabel then
    TLabel(Components[I]).Left:=TLabel(Components[I]).Left + 2;         // shuffle all the labels 2 pixels to the right

// create text area
  Image1.Left:=0;                                      // horizontal position of screen grid
  Image1.Top:=0;                                       // vertical position of screen grid
  Image1.Picture.Bitmap:=TBitmap.Create;               // create a bitmap object for text
  Image1.Picture.Bitmap.Width:=CSR.Width*COLS;         // set width
  Image1.Picture.Bitmap.Height:=CSR.Height*ROWS;       // set height
//Image1.Picture.Bitmap.Canvas.TextFlags:=ETO_OPAQUE;  // opaque writing of text, improves speed             *** NOT SUPPORTED BY LAZARUS ***
  Image1.Picture.Bitmap.Canvas.Font:=CSR.Font;         // copy font details from cursor object
  Image1.Picture.Bitmap.Canvas.Font.Style:=[];         // no underline, etc.
  Image1.Picture.Bitmap.Canvas.Font.Color:=clWhite;    // WHITE text by default
  Image1.Picture.Bitmap.Canvas.Brush.Color:=clBlack;   // default brush: clBlack (for clearing screen)

// create graphics area
  Image2.Left:=Image1.Left;                            // graphics plane overlays text plane
  Image2.Top:=Image1.Top;
  Image2.Picture.Bitmap:=TBitmap.Create;               // create a bitmap object for graphics
  Image2.Picture.Bitmap.Width:=CSR.Width*COLS;         // set width
  Image2.Picture.Bitmap.Height:=CSR.Height*ROWS;       // set height
  Image2.Picture.Bitmap.Canvas.Brush.Color:=clBlack;   // default brush: clBlack (for clearing screen)
  Image2.Picture.Bitmap.Canvas.Pen.Color:=clRed;       // default pen: clRed (for drawing lines)
  Image2.Picture.Bitmap.Canvas.Pen.Width:=1;

// make graphics area transparent
  Image2.Transparent:=true;                            // allow transparency (set in form view)              *** can not be set in form view with LAZARUS
//Image2.Picture.Bitmap.Transparent:=true;             // (doesn't seem to be needed with Lazarus/linux)
  Image2.Picture.Bitmap.TransparentColor:=clBlack;     // transparent colour is black
  Image2.Picture.Bitmap.TransparentMode:=tmFixed;      // use above setting for TC


  Image3.Autosize:=true;
  Image3.Left:=Image1.Left;
  Image3.Top:=Image1.Top;
  Image3.Transparent:=true;
  Image2.Picture.Bitmap.TransparentMode:=tmAuto;
  Image3.Hide;


// configure cursor
  CSR.Top:=Image1.Top;                                 // initial cursor row
  CSR.Left:=Image1.Left;                               // initial cursor column
  CSR.Font.Color:=clRed;                               // cursor colour (FG)
  CSR.Color:=clRed;                                    // cursor colour (BG)

// configure text copy object
  Memo1.Left:=Image1.Left;
  Memo1.Top:=Image1.Top;
(*                                                     // the following are handled
  Memo1.Width:=CSR.Width*COLS;                         // within right-click menu's
  Memo1.Height:=CSR.Height*ROWS;                       /  Item8

  Memo1.Font:=CSR.Font;
  Memo1.Font.Style:=[];
  Memo1.Font.Color:=clBlack;           // black text on
  Memo1.Color:=clSilver;               // a silver background
*)
  Memo1.Hint:='Select the text you want to copy using the mouse,'+#13+
              'then press control-C to copy it to the clipboard.'+#13+
              'When finished, press ENTER to exit this view.';

  Label5.Color:=clLime;                // green 'running' annunciator by default
  Label6.Color:=clYellow;              // yellow 'logging' annunciator by default
  Label7.Color:=clAqua;                // light blue paste annunciator by default

  OpenDialog1.InitialDir:=ExtractFilePath(ExpandFileName(paramstr(0)));
  SaveDialog1.InitialDir:=ExtractFilePath(ExpandFileName(paramstr(0)));

// align graphics and text layers along the Z-axis
  Memo1.Enabled:=false;                // probably not needed
  Memo1.Visible:=false;
  Memo1.SendToBack;

  Image1.Enabled:=false;               // ignore mouse and keyboard events
  Image2.Enabled:=false;               // ignore mouse and keyboard events
  Image3.Enabled:=false;               // ignore mouse and keyboard events

  Image1.BringToFront;                 // text layer ends in background
  Image2.BringToFront;                 // graphics layer overlays text
  Image3.BringToFront;

  CSR.BringToFront;                   // cursor always on top of text and graphics

// create shortcuts
  SCR:=Image1.Picture.Bitmap.Canvas;               // shorthand for canvas object (text screen)
  GFX:=Image2.Picture.Bitmap.Canvas;               // shorthand for canvas object (graphics screen)

  clear(1, 1, COLS, ROWS);             // clear text layer
  GFXclear(0, 0, Gw, Gh);              // clear graphics layer
  gotoxy(1,1);                         // home cursor
  Image2.Hide;                         // graphics layer is turned off initially         *** comment this line out while testing primatives ***
  TS1:=GetTickCount64;
  TS2:=TS1;
  TS3:=TS2;
  TS4:=TS3;

  Timer1.Enabled:=true;
  Timer2.Enabled:=true;
  Timer3.Enabled:=true;
  Timer4.Enabled:=true
end;


procedure TForm1.ApplicationProperties1Activate(Sender: TObject);
begin

  Image3.Left:=Image1.Left;
  Image3.Top:=Image1.Top;
  Image3.Hide
//writeln('application activated')
end;


procedure TForm1.ApplicationProperties1Deactivate(Sender: TObject);
begin
  Image3.Show;
  Image3.Left:=Image1.Left+Image1.Width-Image3.Width;
  Image3.Top:=Image1.Top
//writeln('application deactivated')
end;


procedure TForm1.GenericMouseClick(Sender: TObject);
begin
  writeln('generic mouse click on ',TButton(Sender).Name)
end;


























end.

